import { Injectable } from '@nestjs/common';
import { CreateFinanceDto } from '../dto/create-finance.dto';
import { UpdateFinanceDto } from '../dto/update-finance.dto';
import { FinanceRepository } from '../repositories/finance.repository';
import { Finance } from '../entities/finance.entity';
import { plainToInstance } from 'class-transformer';
import { RequestContext } from 'src/shared/request-context/request-context.dto';
import { QueryFinanceDto } from '../dto/query-finance.dto';

import { Between, LessThanOrEqual } from 'typeorm';
import { TimeRangeType, TurnoverType } from '../enums/finance.enum';
import {
  dateToKeyStringArr,
  getDayCount,
  getLastStartDateObj,
  dateToMysqlDateTimeString,
  calculateStartAndEnd,
} from '../helpers/date.helper';
import {
  SortedFinanceItemType,
  SortedFinanceTempType,
} from '../types/finnance-item';

import Big from 'big.js';
import { User } from 'src/modules/user/entities/user.entity';
import { Usage } from 'src/modules/usage/entities/usage.entity';

@Injectable()
export class FinanceService {
  constructor(private repository: FinanceRepository) {}

  async create(createFinanceDto: CreateFinanceDto, ctx: RequestContext) {
    const finance = plainToInstance(Finance, createFinanceDto);
    // const finance = plainToInstance(Finance, createFinanceDto);
    const usage = plainToInstance(Usage, { id: createFinanceDto.usageId });
    finance.user = plainToInstance(User, ctx.user);
    finance.usage = usage;

    // this.logger.log(ctx, `calling ${UserRepository.name}.saveUser`);
    try {
      await this.repository.save(finance);
    } catch (err: any) {
      console.log('--------create err: ', JSON.stringify(err));
      throw err;
    }

    return 'successfull';
  }

  // 记账首页
  async findAccCard(ctx: RequestContext) {
    const start = new Date();
    const dayEnd = new Date(start);
    const tmpDate = new Date(start);
    tmpDate.setHours(0);
    tmpDate.setMinutes(0);
    const dayStart = new Date(tmpDate);
    tmpDate.setDate(1);
    const firstDayOfCurMonth = new Date(tmpDate);

    const curDayInflow =
      (await this.repository.sum('number', {
        turnoverType: TurnoverType.INFLOW,
        user: {
          id: ctx.user.id,
        },
        createdAt: Between(dayStart, dayEnd),
      })) || 0;

    const curDayExpenditure =
      (await this.repository.sum('number', {
        turnoverType: TurnoverType.EXPENDITURE,
        user: {
          id: ctx.user.id,
        },
        createdAt: Between(dayStart, dayEnd),
      })) || 0;

    console.log(
      '---------firstDayOfCurMonth, dayEnd: ',
      firstDayOfCurMonth,
      dayEnd,
    );

    const curMonthInflow =
      (await this.repository.sum('number', {
        turnoverType: TurnoverType.INFLOW,
        user: {
          id: ctx.user.id,
        },
        createdAt: Between(firstDayOfCurMonth, dayEnd),
      })) || 0;

    const curMonthExpenditure =
      (await this.repository.sum('number', {
        turnoverType: TurnoverType.EXPENDITURE,
        user: {
          id: ctx.user.id,
        },
        createdAt: Between(firstDayOfCurMonth, dayEnd),
      })) || 0;

    return [
      curDayExpenditure,
      curDayInflow,
      curMonthExpenditure,
      curMonthInflow,
    ];
  }

  // 记账首页
  async findAccList(ctx: RequestContext, query: QueryFinanceDto) {
    const { offset, limit } = query;
    const start = new Date();

    console.log('-----FinanceService------findAccList: ', query);

    const res = await this.repository.findAndCount({
      // relations: ['user'],
      relations: ['usage'],
      where: {
        user: {
          id: ctx.user.id,
        },
        createdAt: LessThanOrEqual(start),
        // user: ctx.user.id,
        // createdAt:
      },
      skip: (offset - 1) * limit,
      take: limit,
      order: {
        createdAt: 'DESC',
      },
    });

    console.log('-----FinanceService------findAccList res: ', res);
    const [data, total] = res;

    console.log('total: ', total);
    // console.log('---data: ', data);
    console.log(
      '---data typeof data[0]?.createdAt: ',
      typeof data[0]?.createdAt,
    );
    console.log(
      '---data typeof data[0]?.createdAt: ',
      data[0]?.createdAt instanceof Date,
    );
    console.log('---data: ', data[0]);
    // if (total) {
    //   data.sort(
    //     (a: Finance, b: Finance) =>
    //       b.createdAt.getTime() - a.createdAt.getTime(),
    //   );
    // }

    const tempObj: SortedFinanceTempType = {};
    let order = 0;
    const len = data.length;

    for (let i = 0; i < len; i++) {
      const finance = data[i];
      const caArr = dateToKeyStringArr(finance.createdAt);
      const key = caArr[0];
      if (!tempObj[key]) {
        tempObj[key] = {
          data: [finance],
          order: order++,
          dateKey1: caArr[0],
          dateKey2: caArr[1],
          dateKey3: caArr[2],
        };
      } else {
        tempObj[key].data.push(finance);
      }
    }

    const arr: Array<SortedFinanceItemType> = [];
    Object.keys(tempObj).forEach((k) => {
      let totalInflow = 0;
      let totalExpenditure = 0;
      const finObj = tempObj[k];
      const len = finObj.data.length;
      for (let i = 0; i < len; i++) {
        const fin = finObj.data[i];
        if (fin.turnoverType === TurnoverType.INFLOW) {
          totalInflow = Big(totalInflow).plus(Big(fin.number)).toNumber();
          console.log(
            `--INFLOW---- len : ${len}-- i : ${i}--- totalInflow: ${totalInflow}-----  fin.number: ${fin.number}-`,
          );
          // totalInflow += fin.number;
        } else {
          totalExpenditure = Big(totalExpenditure)
            .plus(Big(fin.number))
            .toNumber();

          console.log(
            `--EXPENDITURE---- len : ${len}-- i : ${i}--- totalInflow: ${totalExpenditure}-----  fin.number: ${fin.number}-`,
          );
        }
      }

      console.log('*'.repeat(20));

      finObj.totalInflow = totalInflow;
      finObj.totalExpenditure = totalExpenditure;

      arr.push(finObj);
    });

    arr.sort((a, b) => a.order - b.order);

    return [arr, total];
  }

  // findAll(ctx: RequestContext, limit: number, offset: number) {
  findAll(ctx: RequestContext, query: QueryFinanceDto) {
    // findAll(ctx: RequestContext, limit: number, offset: number) {
    console.log('-----findAll----ctx user: ');
    console.log(ctx);
    console.log(query);
    console.log('-----findAll----ctx user: ');

    return this.repository.find({
      select: {
        turnoverType: true,
        number: true,
        createdAt: true,
      },
      // relations: ['user'],
      relations: ['usage'],
      where: {
        turnoverType: TurnoverType.INFLOW,
        user: {
          id: ctx.user.id,
        },
        createdAt: LessThanOrEqual(query.start),
        // user: ctx.user.id,
        // createdAt:
      },
    });
  }

  // 算 staCard1 卡片1
  async findCard1(ctx: RequestContext, query: QueryFinanceDto) {
    const { turnoverType, start, end, timeRangeType } = query;

    let curInFlow = 0;
    let curInFlowOrExpenditure = 0;
    let curExpenditure = 0;
    let average = 0;
    let balance = 0;

    let lastInFlowOrExpenditure = 0;
    // let lastExpenditure = 0;
    let compareToLast = 0;

    const lastStart = getLastStartDateObj(start, timeRangeType);
    const lastEnd = new Date(start);

    // cur week
    // inflow
    curInFlow =
      (await this.repository.sum('number', {
        turnoverType: TurnoverType.INFLOW,
        user: {
          id: ctx.user.id,
        },
        createdAt: Between(start, end),
      })) || 0;

    // expenditure 本周支出
    curExpenditure =
      (await this.repository.sum('number', {
        turnoverType: TurnoverType.EXPENDITURE,
        user: {
          id: ctx.user.id,
        },
        createdAt: Between(start, end),
        // user: ctx.user.id,
        // createdAt:
      })) || 0;

    if (curInFlow === null || curExpenditure === null) {
      throw new Error('curInFlow === null|| curExpenditure === null');
    }

    curInFlowOrExpenditure =
      turnoverType === TurnoverType.EXPENDITURE ? curExpenditure : curInFlow;

    // 日均支出
    const day = getDayCount(end, start);
    average = Number((curInFlowOrExpenditure / day).toFixed(2));

    //  收支结余
    balance = Number((curInFlow - curExpenditure).toFixed(2));

    // last week

    lastInFlowOrExpenditure =
      (await this.repository.sum('number', {
        turnoverType,
        user: {
          id: ctx.user.id,
        },
        createdAt: Between(lastStart, lastEnd),
      })) || 0;

    if (lastInFlowOrExpenditure === null) {
      throw new Error('lastExpenditure === null');
    }

    //  结余
    compareToLast = curInFlowOrExpenditure - lastInFlowOrExpenditure;

    return [curInFlowOrExpenditure, average, compareToLast, balance];
  }

  /**

  SELECT
    DATE(order_date) AS day,
    YEAR(order_date) AS year,
    MONTH(order_date) AS month,
    product_id,
    SUM(quantity) AS total_quantity
  FROM
      orders
  GROUP BY
      day, year, month, product_id
  ORDER BY
      year, month, product_id;

   */

  // 算 卡片2
  // https://deepinout.com/mysql/mysql-questions/1984_mysql_aggregating_data_by_timespan_in_mysql.html
  // TODO: fix 此处 代码 存在 SQL 代码注入风险
  async findCard2(ctx: RequestContext, query: QueryFinanceDto) {
    const { turnoverType, timeRangeType } = query;
    const [start, end] = calculateStartAndEnd(query);
    // 周显示7 天 月显示 12个月 年显示 5 年 前后 推 2 年
    // console.log('------findCard2 typeof start: ', typeof start);

    const genSQL = (
      fn: 'DATE' | 'MONTH' | 'YEAR',
      turnoverType: TurnoverType,
      userId: number,
      start: Date,
      end: Date,
    ) => {
      const ALIAS_MAP = {
        DATE: 'day',
        MONTH: 'month',
        YEAR: 'year',
      };
      return `
        SELECT 
          ${fn}(createdAt) as ${ALIAS_MAP[fn]},
          SUM(number) AS number
        From finances 
        WHERE 
          finances.userId = ${userId} 
          AND turnoverType = '${turnoverType}' 
          AND 
            createdAt BETWEEN '${dateToMysqlDateTimeString(start)}' AND '${dateToMysqlDateTimeString(end)}'
          GROUP BY ${ALIAS_MAP[fn]} ORDER BY ${ALIAS_MAP[fn]} DESC;
      `;
    };

    const userId = ctx.user.id;
    const SELECT_MAP = {
      [TimeRangeType.Week]: genSQL('DATE', turnoverType, userId, start, end),
      [TimeRangeType.Month]: genSQL('MONTH', turnoverType, userId, start, end),
      [TimeRangeType.Year]: genSQL('YEAR', turnoverType, userId, start, end),
    };
    // this.repository.manager.query

    try {
      const res = await this.repository.manager.query(
        SELECT_MAP[timeRangeType],
      );
      console.log('----service--findCard2: ' + JSON.stringify(res));
      return res;
    } catch (error) {
      console.log('error: ', error);
      throw error;
    }
  }

  // 算 卡片3
  async findCard3(ctx: RequestContext, query: QueryFinanceDto) {
    const { turnoverType, start, end } = query;

    const SQL = `
      SELECT 
        u.name AS name, 
        SUM(number) AS value 
      From finances AS f, usages AS u 
      WHERE 
        f.usageId = u.id 
        AND f.userId = ${ctx.user.id} 
        AND turnoverType = '${turnoverType}' 
        AND 
        createdAt BETWEEN '${dateToMysqlDateTimeString(start)}' AND '${dateToMysqlDateTimeString(end)}' 
      GROUP BY name 
      ORDER BY value;
    `;

    try {
      const res = await this.repository.manager.query(SQL);
      return res;
    } catch (error) {
      console.log('----error: ', error);
      throw error;
    }
  }

  findOne(id: number) {
    return `This action returns a #${id} finance`;
  }

  update(id: number, updateFinanceDto: UpdateFinanceDto) {
    return `This action updates a #${id} finance`;
  }

  remove(id: number) {
    return `This action removes a #${id} finance`;
  }
}
